using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Text;

namespace Bread.IoC;

internal record struct InvocationEntry(string InvocationText, string? TargetSymbol, string TreeFilePath);

internal sealed class ServiceEntry
{
    public ServiceEntry(ITypeSymbol intf, ITypeSymbol impl, bool isInstance, string? instanceExpr, InvocationExpressionSyntax syntax)
    {
        Interface = intf;
        Implementation = impl;
        IsInstance = isInstance;
        InstanceExpression = instanceExpr;
        Syntax = syntax;
    }

    public ITypeSymbol Interface { get; }
    public ITypeSymbol Implementation { get; }
    public bool IsInstance { get; }
    public string? InstanceExpression { get; }
    public InvocationExpressionSyntax Syntax { get; }
}


internal class CompilationParser
{
    private readonly Compilation _compilation;
    public CompilationParser(Compilation compilation)
    {
        _compilation = compilation;
    }

    public List<InvocationExpressionSyntax> GetRegistMethods(ContainerInfo info)
    {
        var entries = new List<InvocationExpressionSyntax>();

        // Try to locate the type symbol by metadata name first
        INamedTypeSymbol? typeSymbol = null;
        var metadataName = string.IsNullOrEmpty(info.Namespace) ? info.ClassName : $"{info.Namespace}.{info.ClassName}";
        typeSymbol = _compilation.GetTypeByMetadataName(metadataName);

        // Fallback: search syntax trees and semantic models
        if (typeSymbol == null) {
            foreach (var tree in _compilation.SyntaxTrees) {
                var root = tree.GetRoot();
                var classNodes = root.DescendantNodes().OfType<ClassDeclarationSyntax>().Where(c => c.Identifier.Text == info.ClassName);
                if (!classNodes.Any()) continue;

                var model = _compilation.GetSemanticModel(tree);
                foreach (var cls in classNodes) {
                    var sym = model.GetDeclaredSymbol(cls) as INamedTypeSymbol;
                    if (sym == null) continue;
                    if ((sym.ContainingNamespace?.ToDisplayString() ?? string.Empty) == info.Namespace) {
                        typeSymbol = sym;
                        break;
                    }
                }
                if (typeSymbol != null) break;
            }
        }

        if (typeSymbol == null) {
            // Nothing to do: type not found
            return entries;
        }

        // Find the Bootstrap override method symbol (single parameter of the expected interface)
        var bootstrapMethod = typeSymbol.GetMembers().OfType<IMethodSymbol>()
            .FirstOrDefault(m =>
                m.Name == "Bootstrap" &&
                m.Parameters.Length == 1 &&
                (m.Parameters[0].Type.Name == "IBootstrapper")
            );

        if (bootstrapMethod == null) return entries;


        // For each declaring syntax (handles partial classes / partial methods), enumerate invocation expressions
        foreach (var declRef in bootstrapMethod.DeclaringSyntaxReferences) {
            if (declRef == null) continue;
            var node = declRef.GetSyntax() as MethodDeclarationSyntax;
            if (node == null) continue;

            var tree = node.SyntaxTree;
            var model = _compilation.GetSemanticModel(tree);

            var invocations = node.DescendantNodes().OfType<InvocationExpressionSyntax>();
            entries.AddRange(invocations);
        }

        return entries;
    }

    public List<ServiceEntry> GetRegistMethodsSynx(List<InvocationExpressionSyntax> invocations)
    {
        // local service entry
        var entries = new List<ServiceEntry>();

        foreach (var inv in invocations) {
            if (inv == null) continue;

            // semantic model for this invocation
            var tree = inv.SyntaxTree;
            var model = _compilation.GetSemanticModel(tree);

            // expression can be:
            //  - ioc.Regist<T>()
            //  - Regist<T>()
            // so inspect MemberAccessExpression or GenericName directly
            GenericNameSyntax? generic = null;
            if (inv.Expression is MemberAccessExpressionSyntax member) {
                generic = member.Name as GenericNameSyntax;
            }
            else if (inv.Expression is GenericNameSyntax gn) {
                generic = gn;
            }
            else if (inv.Expression is IdentifierNameSyntax id && id.Identifier.Text == "Regist") {
                // Regist without type args: unlikely given IBootstrapper signature, skip
                generic = null;
            }

            if (generic == null) continue;
            if (generic.Identifier.Text != "Regist") continue;

            // parse type arguments
            var args = generic.TypeArgumentList.Arguments;
            ITypeSymbol? interfaceSymbol = null;
            ITypeSymbol? implementationSymbol = null;
            bool isInstance = inv.ArgumentList.Arguments.Count > 0;
            string? instanceExpression = null;

            if (args.Count == 1) {
                // Regist<TImplementation>() or Regist<TImplementation>(instance)
                var t = model.GetTypeInfo(args[0]).Type;
                implementationSymbol = t;
                interfaceSymbol = t; // implicit mapping to itself
            }
            else if (args.Count == 2) {
                var tIntf = model.GetTypeInfo(args[0]).Type;
                var tImpl = model.GetTypeInfo(args[1]).Type;
                interfaceSymbol = tIntf;
                implementationSymbol = tImpl;
            }
            else {
                // unexpected shape - skip
                continue;
            }

            if (isInstance) {
                // try to get instance expression text and its type
                var firstArg = inv.ArgumentList.Arguments[0];
                instanceExpression = firstArg.ToString();
                var instType = model.GetTypeInfo(firstArg.Expression).Type;
                if (implementationSymbol == null && instType != null) {
                    implementationSymbol = instType;
                }
            }

            if (interfaceSymbol == null || implementationSymbol == null) continue;

            entries.Add(new ServiceEntry(interfaceSymbol, implementationSymbol, isInstance, instanceExpression, inv));
        }

        return entries;
    }
}

